using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using OFX.Aggregator.Infrastructure;

namespace OFX.Aggregator.Implementation
{
	public class XmlBasedStatementPostProcessor : IStatementProcessor
	{
		private static readonly Type enumType = typeof(Enum);
		private static readonly Type statementLineType = typeof(StatementLine);
		private static readonly Type stringType = typeof(string);
		private readonly string rulesXmlPath;

		public XmlBasedStatementPostProcessor(string rulesXmlPath)
		{
			this.rulesXmlPath = rulesXmlPath;
		}

		#region Implementation of IStatementProcessor

		public void Process(Statement statement)
		{
			XElement rulesFromFile = XElement.Load(this.rulesXmlPath);

			IEnumerable<XElement> rules =
				from rule in rulesFromFile.Descendants("rule")
				select rule;

			var propertyToInfoDictionary = new Dictionary<string, PropertyInfo>();

			foreach (XElement rule in rules)
			{
				XElement matchElement = rule.Element("match");
				string matchFieldName = matchElement.Attribute("field").Value;
				string matchPattern = matchElement.Value;
				RegexOptions options = RegexOptions.Singleline;
				var regex = new Regex(matchPattern, options);

				PropertyInfo matchPropertyInfo = getPropertyInfo(propertyToInfoDictionary, matchFieldName);

				foreach (PaymentOriginator paymentOriginator in statement.PaymentOriginators)
				{
					foreach (StatementLine statementLine in paymentOriginator.StatementLines)
					{
						var value = (string) matchPropertyInfo.GetValue(statementLine, null);

						Match match = regex.Match(value);
						if (!match.Success)
							continue;

						IEnumerable<XElement> changeRules =
							from changeRule in rule.Descendants("then").Descendants("change-statement-line")
							select changeRule;

						foreach (XElement changeRule in changeRules)
						{
							string assignee = changeRule.Attribute("field").Value;
							object assignValue = changeRule.Value;

							PropertyInfo assigneePropertyInfo = getPropertyInfo(propertyToInfoDictionary, assignee);
							if (assigneePropertyInfo.PropertyType.BaseType == enumType)
								assignValue = Enum.Parse(assigneePropertyInfo.PropertyType, (string) assignValue);
							else if (assigneePropertyInfo.PropertyType != stringType)
								assignValue = Convert.ChangeType(assignValue, assigneePropertyInfo.PropertyType);

							if (match.Groups.Count > 1)
							{
								assignValue = match.Result((string) assignValue);
							}

							assigneePropertyInfo.SetValue(statementLine, assignValue, null);
						}
					}
				}
			}
		}

		private static PropertyInfo getPropertyInfo(
			IDictionary<string, PropertyInfo> propertyToInfoDictionary,
			string matchFieldName)
		{
			PropertyInfo propertyInfo;

			if (!propertyToInfoDictionary.TryGetValue(matchFieldName, out propertyInfo))
			{
				propertyInfo = statementLineType.GetProperty(matchFieldName);
				propertyToInfoDictionary.Add(matchFieldName, propertyInfo);
			}

			return propertyInfo;
		}

		#endregion
	}
}